﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;

namespace RainEngine.SceneManagement
{
    /// <summary>
    /// 绘制由线段组成的不同形状的类。
    /// </summary>
    /// <remarks>
    /// ShapeManager用于在一个批次内绘制由线段组成的形状。
    /// 通过许多Add* 方法将形状添加到集合中，然后再一次Draw调用中绘制所有形状。
    /// Draw will cause all shapes to be rendered. This mechanism was chosen because it allows
    /// game code to call the Add* methods wherever is most convenient, rather than having to
    /// add draw methods to all of the necessary objects.
    /// 
    /// Additionally the renderer supports a lifetime for all shapes added. This allows for things
    /// like visualization of raycast bullets. The game would call the AddLine overload with the
    /// lifetime parameter and pass in a positive value. The renderer will then draw that shape
    /// for the given amount of time without any more calls to AddLine being required.
    /// 
    /// The renderer's batching mechanism uses a cache system to avoid garbage and also draws as
    /// many lines in one call to DrawUserPrimitives as possible. If the renderer is trying to draw
    /// more lines than are allowed in the Reach profile, it will break them up into multiple draw
    /// calls to make sure the game continues to work for any game.</remarks>
	public class ShapeManager
	{
        // 一个单独的形状，可以是线、长方体、视锥体、球
		class Shape
		{
            /// <summary>
            /// 形状使用的顶点数组。
            /// </summary>
			public VertexPositionColor[] Vertices;

            /// <summary>
            /// 形状的线条数量。
            /// </summary>
			public int LineCount;

            /// <summary>
            /// 形状显示的持续时间
            /// </summary>
			public float Lifetime;
		}
        
        // 使用一个缓存重用Shape对象，这样可以避免垃圾回收
		readonly List<Shape> cachedShapes = new List<Shape>();
		readonly List<Shape> activeShapes = new List<Shape>();

        // 保存顶点的数组，它的大小可以根据需要进行调整
        VertexPositionColor[] verts = new VertexPositionColor[64];

        // 绘制shape使用的图形设备
        GraphicsDevice graphicsDevice;		
        private Effect effect;
        EffectParameter wvp;

        // 保存包围盒和视锥体的顶点的数组
		Vector3[] corners = new Vector3[8];

        // 保存单位包围球使用的顶点数组
        const int sphereResolution = 30;
        const int sphereLineCount = (sphereResolution + 1) * 3;
        static Vector3[] unitSphere;

        public ShapeManager(GraphicsDevice device)
        {
            this.graphicsDevice = device;    

            effect = new Effect(graphicsDevice, Resource.LineRenderingEffectBin);
            wvp = effect.Parameters["WorldViewProj"];

            // 创建球的顶点
            InitializeSphere();
        }

        /// <summary>
        /// 初始化。
        /// </summary>
        /// <param name="setGraphicsDevice">图形设备。</param>
		public void Initialize(GraphicsDevice setGraphicsDevice)
		{
            // 判断是否需要对图形设备进行初始化
            if (setGraphicsDevice != null)
                throw new InvalidOperationException("Initialize只能被调用一次。");

            // 保存图形设备
            graphicsDevice = setGraphicsDevice;
            // 创建球的顶点
            InitializeSphere();
		}

        /// <summary>
        /// 添加一条直线，只持续一帧。
        /// </summary>
        /// <param name="start">直线的起点坐标。</param>
        /// <param name="end">直线的终点坐标。</param>
        /// <param name="color">直线颜色。</param>
		public void AddLine(Vector3 start, Vector3 end, Color color)
		{
            AddLine(start, end, color, 0f);
		}

        /// <summary>
        /// 添加一条直线并设置持续时间。
        /// </summary>
        /// <param name="start">直线的起点坐标。</param>
        /// <param name="end">直线的终点坐标。</param>
        /// <param name="color">直线颜色。</param>
        /// <param name="life">以秒为单位的持续时间。</param>
		public void AddLine(Vector3 start, Vector3 end, Color color, float life)
        {
            // 创建绘制直线的Shape
            Shape shape = GetShapeForLines(1, life);

            // 添加直线的两个顶点
			shape.Vertices[0] = new VertexPositionColor(start, color);
			shape.Vertices[1] = new VertexPositionColor(end, color);
		}

        /// <summary>
        /// 添加一个三角形，只持续一帧。
        /// </summary>
        /// <param name="a">三角形的第一个顶点坐标。</param>
        /// <param name="b">三角形的第二个顶点坐标。</param>
        /// <param name="c">三角形的第三个顶点坐标。</param>
        /// <param name="color">线条颜色。</param>
		public void AddTriangle(Vector3 a, Vector3 b, Vector3 c, Color color)
		{
			AddTriangle(a, b, c, color, 0f);
		}

        /// <summary>
        /// 添加一个三角形并设置持续时间。
        /// </summary>
        /// <param name="a">三角形的第一个顶点坐标。</param>
        /// <param name="b">三角形的第二个顶点坐标。</param>
        /// <param name="c">三角形的第三个顶点坐标。</param>
        /// <param name="color">三角形线条颜色。</param>
        /// <param name="life">以秒为单位的持续时间。</param>
		public void AddTriangle(Vector3 a, Vector3 b, Vector3 c, Color color, float life)
        {
            // 创建绘制三角形的Shape
            Shape shape = GetShapeForLines(3, life);

            // 将顶点添加到shape中
			shape.Vertices[0] = new VertexPositionColor(a, color);
			shape.Vertices[1] = new VertexPositionColor(b, color);
			shape.Vertices[2] = new VertexPositionColor(b, color);
			shape.Vertices[3] = new VertexPositionColor(c, color);
			shape.Vertices[4] = new VertexPositionColor(c, color);
			shape.Vertices[5] = new VertexPositionColor(a, color);
		}

        /// <summary>
        /// 添加一个视锥体，只持续一帧。
        /// </summary>
        /// <param name="frustum">要绘制的视锥体。</param>
        /// <param name="color">视锥体的线条颜色。</param>
		public void AddBoundingFrustum(BoundingFrustum frustum, Color color)
		{
			AddBoundingFrustum(frustum, color, 0f);
		}

        /// <summary>
        /// 添加一个视锥体并设置持续时间。
        /// </summary>
        /// <param name="frustum">要绘制的视锥体。</param>
        /// <param name="color">视锥体的线条颜色。</param>
        /// <param name="life">以秒为单位的持续时间。</param>
		public void AddBoundingFrustum(BoundingFrustum frustum, Color color, float life)
        {
            // 创建绘制视锥体的Shape
            Shape shape = GetShapeForLines(12, life);

            // 获取视锥体的顶点
			frustum.GetCorners(corners);

            // 设置视锥体底面的顶点
			shape.Vertices[0] = new VertexPositionColor(corners[0], color);
			shape.Vertices[1] = new VertexPositionColor(corners[1], color);
			shape.Vertices[2] = new VertexPositionColor(corners[1], color);
			shape.Vertices[3] = new VertexPositionColor(corners[2], color);
			shape.Vertices[4] = new VertexPositionColor(corners[2], color);
			shape.Vertices[5] = new VertexPositionColor(corners[3], color);
			shape.Vertices[6] = new VertexPositionColor(corners[3], color);
			shape.Vertices[7] = new VertexPositionColor(corners[0], color);

            // 设置视锥体顶面的顶点
			shape.Vertices[8] = new VertexPositionColor(corners[4], color);
			shape.Vertices[9] = new VertexPositionColor(corners[5], color);
			shape.Vertices[10] = new VertexPositionColor(corners[5], color);
			shape.Vertices[11] = new VertexPositionColor(corners[6], color);
			shape.Vertices[12] = new VertexPositionColor(corners[6], color);
			shape.Vertices[13] = new VertexPositionColor(corners[7], color);
			shape.Vertices[14] = new VertexPositionColor(corners[7], color);
			shape.Vertices[15] = new VertexPositionColor(corners[4], color);

            // 设置视锥体竖直面的顶点
			shape.Vertices[16] = new VertexPositionColor(corners[0], color);
			shape.Vertices[17] = new VertexPositionColor(corners[4], color);
			shape.Vertices[18] = new VertexPositionColor(corners[1], color);
			shape.Vertices[19] = new VertexPositionColor(corners[5], color);
			shape.Vertices[20] = new VertexPositionColor(corners[2], color);
			shape.Vertices[21] = new VertexPositionColor(corners[6], color);
			shape.Vertices[22] = new VertexPositionColor(corners[3], color);
			shape.Vertices[23] = new VertexPositionColor(corners[7], color);
		}

        /// <summary>
        /// 添加一个包围盒，只绘制一帧。
        /// </summary>
        /// <param name="box">要绘制的包围盒。</param>
        /// <param name="color">包围盒的线条颜色。</param>
		public void AddBoundingBox(BoundingBox box, Color color)
		{
			AddBoundingBox(box, color, 0f);
		}

        /// <summary>
        /// 添加一个包围盒并设置持续时间。
        /// </summary>
        /// <param name="box">要绘制的包围盒。</param>
        /// <param name="color">包围盒的线条颜色。</param>
        /// <param name="life">以秒为单位的持续时间。</param>
		public void AddBoundingBox(BoundingBox box, Color color, float life)
		{
            // 创建绘制包围盒的Shape
			Shape shape = GetShapeForLines(12, life);

            // 获取包围盒的顶点
			box.GetCorners(corners);

			// 设置盒底面的顶点
			shape.Vertices[0] = new VertexPositionColor(corners[0], color);
			shape.Vertices[1] = new VertexPositionColor(corners[1], color);
			shape.Vertices[2] = new VertexPositionColor(corners[1], color);
			shape.Vertices[3] = new VertexPositionColor(corners[2], color);
			shape.Vertices[4] = new VertexPositionColor(corners[2], color);
			shape.Vertices[5] = new VertexPositionColor(corners[3], color);
			shape.Vertices[6] = new VertexPositionColor(corners[3], color);
			shape.Vertices[7] = new VertexPositionColor(corners[0], color);

			// 设置盒顶面的顶点
			shape.Vertices[8] = new VertexPositionColor(corners[4], color);
			shape.Vertices[9] = new VertexPositionColor(corners[5], color);
			shape.Vertices[10] = new VertexPositionColor(corners[5], color);
			shape.Vertices[11] = new VertexPositionColor(corners[6], color);
			shape.Vertices[12] = new VertexPositionColor(corners[6], color);
			shape.Vertices[13] = new VertexPositionColor(corners[7], color);
			shape.Vertices[14] = new VertexPositionColor(corners[7], color);
			shape.Vertices[15] = new VertexPositionColor(corners[4], color);

			// 设置盒竖直面的顶点
			shape.Vertices[16] = new VertexPositionColor(corners[0], color);
			shape.Vertices[17] = new VertexPositionColor(corners[4], color);
			shape.Vertices[18] = new VertexPositionColor(corners[1], color);
			shape.Vertices[19] = new VertexPositionColor(corners[5], color);
			shape.Vertices[20] = new VertexPositionColor(corners[2], color);
			shape.Vertices[21] = new VertexPositionColor(corners[6], color);
			shape.Vertices[22] = new VertexPositionColor(corners[3], color);
			shape.Vertices[23] = new VertexPositionColor(corners[7], color);
		}

        /// <summary>
        /// 添加一个包围球，只绘制一帧。
        /// </summary>
        /// <param name="sphere">要绘制的包围球。</param>
        /// <param name="color">包围球的线条颜色。</param>
        public void AddBoundingSphere(BoundingSphere sphere, Color color)
        {
            AddBoundingSphere(sphere, color, 0f);
        }

        /// <summary>
        /// 添加一个包围球并设置持续时间。
        /// </summary>
        /// <param name="sphere">要绘制的包围球。</param>
        /// <param name="color">包围球的线条颜色。</param>
        /// <param name="life">以秒为单位的持续时间。</param>
        public void AddBoundingSphere(BoundingSphere sphere, Color color, float life)
        {
            // 创建包围球的Shape
            Shape shape = GetShapeForLines(sphereLineCount, life);

            // 设置球的顶点
            for (int i = 0; i < unitSphere.Length; i++)
            {
                // 基于球的中心和半径变换顶点的坐标
                Vector3 vertPos = unitSphere[i] * sphere.Radius + sphere.Center;

                // 将顶点添加到shape中
                shape.Vertices[i] = new VertexPositionColor(vertPos, color);
            }
        }

        /// <summary>
        /// 绘制所有添加到Draws the shapes that were added to the renderer and are still alive.
        /// </summary>
        /// <param name="gameTime">当前的游戏时间。</param>
        /// <param name="view">视矩阵</param>
        /// <param name="projection">投影矩阵</param>
		public void Draw(GameTime gameTime,Matrix view,Matrix projection)
		{
            // 更新effect的矩阵
            wvp.SetValue(Matrix.Identity * view * projection);

            // 计算顶点的数量。
            int vertexCount = 0;
            foreach (var shape in activeShapes)
                vertexCount += shape.LineCount * 2;

            // 如果顶点数量数量大于则需要进行绘制
            if (vertexCount > 0)
			{
                // 判断顶点数组大小是否小于顶点数量
                if (verts.Length < vertexCount)
                {
                    // 如果小于，则我们需要改变顶点数组的大小，
                    // 我们将顶点数组的大小设置为顶点数量的两倍，这样就能避免以后可能的重新调整大小的情况。
                    verts = new VertexPositionColor[vertexCount * 2];
                }

                // 现在再次遍历activeShapes将顶点移动到顶点数组中，
                // 并累加要绘制的线条数量。
                int lineCount = 0;
                int vertIndex = 0;
                foreach (Shape shape in activeShapes)
                {
                    lineCount += shape.LineCount;
                    int shapeVerts = shape.LineCount * 2;
                    for (int i = 0; i < shapeVerts; i++)
                        verts[vertIndex++] = shape.Vertices[i];
                }

                // 开始进行绘制
				effect.CurrentTechnique.Passes[0].Apply();

                // We draw in a loop because the Reach profile only supports 65,535 primitives. While it's
                // not incredibly likely, if a game tries to render more than 65,535 lines we don't want to
                // crash. We handle this by doing a loop and drawing as many lines as we can at a time, capped
                // at our limit. 然后在顶点数组中向前移动绘制后面的线条。
                int vertexOffset = 0;
                while (lineCount > 0)
                {
                    // 计算需要绘制的线条数量
                    int linesToDraw = Math.Min(lineCount, 65535);

                    // 绘制线条
                    graphicsDevice.DrawUserPrimitives(PrimitiveType.LineList, verts, vertexOffset, linesToDraw);

                    // 基于绘制的线条数量改变顶点的偏移
                    vertexOffset += linesToDraw * 2;

                    // 从线条总数量中移除已绘制的线条数量
                    lineCount -= linesToDraw;
                }
			}

            // 遍历activeShapes集合，将所有过期的shape移动到cachedShapes集合中。 
			bool resort = false;
			for (int i = activeShapes.Count - 1; i >= 0; i--)
			{
				Shape s = activeShapes[i];
                s.Lifetime -= (float)gameTime.ElapsedGameTime.TotalSeconds;
				if (s.Lifetime <= 0)
				{
					cachedShapes.Add(s);
					activeShapes.RemoveAt(i);
					resort = true;
				}
			}

            // 如果进行了shape的移动，则需要重新对cachedShapes集合进行排序，
            // 确保最小的shape处于集合的最前面。
			if (resort)
				cachedShapes.Sort(CachedShapesSort);
        }
        
        /// <summary>
        /// 创建一个单位球的顶点数组。
        /// </summary>
        private void InitializeSphere()
        {
            // 一条直线需要两个顶点
            unitSphere = new Vector3[sphereLineCount * 2];

            // 计算每个圆周的步长
            float step = MathHelper.TwoPi / sphereResolution;

            // 用于保存顶点数组的索引
            int index = 0;

            // 首先创建XY平面上的顶点
            for (float a = 0f; a < MathHelper.TwoPi; a += step)
            {
                unitSphere[index++] = new Vector3((float)Math.Cos(a), (float)Math.Sin(a), 0f);
                unitSphere[index++] = new Vector3((float)Math.Cos(a + step), (float)Math.Sin(a + step), 0f);
            }

            // 然后是XZ平面
            for (float a = 0f; a < MathHelper.TwoPi; a += step)
            {
                unitSphere[index++] = new Vector3((float)Math.Cos(a), 0f, (float)Math.Sin(a));
                unitSphere[index++] = new Vector3((float)Math.Cos(a + step), 0f, (float)Math.Sin(a + step));
            }

            // 最后是YZ平面
            for (float a = 0f; a < MathHelper.TwoPi; a += step)
            {
                unitSphere[index++] = new Vector3(0f, (float)Math.Cos(a), (float)Math.Sin(a));
                unitSphere[index++] = new Vector3(0f, (float)Math.Cos(a + step), (float)Math.Sin(a + step));
            }
        }

        /// <summary>
        /// 基于顶点数组的大小对缓存集合cachedShapes进行排序。
        /// </summary>
        private int CachedShapesSort(Shape s1, Shape s2)
        {
            return s1.Vertices.Length.CompareTo(s2.Vertices.Length);
        }

        /// <summary>
        /// 根据给定的线条数量和持续时间获取一个Shape对象。
        /// </summary>
        private Shape GetShapeForLines(int lineCount, float life)
        {
            Shape shape = null;

            // 遍历缓存集合cachedShapes，如果一个shape是否包含足够大小的数组可以容纳给定的线条数量，
            // 如果是，则将它从缓存集合cachedShapes中移动到activeShapes集合中并跳出循环。
            int vertCount = lineCount * 2;
            for (int i = 0; i < cachedShapes.Count; i++)
            {
                if (cachedShapes[i].Vertices.Length >= vertCount)
                {
                    shape = cachedShapes[i];
                    cachedShapes.RemoveAt(i);
                    activeShapes.Add(shape);
                    break;
                }
            }

            // 如果在缓存集合cachedShapes中无法找到这样的shape，我们就需要创建一个新shape，
            // 并将它添加到activeShapes集合中。
            if (shape == null)
            {
                shape = new Shape { Vertices = new VertexPositionColor[vertCount] };
                activeShapes.Add(shape);
            }

            // 基于输入参数设置线条的直线数量和lifetime。
            shape.LineCount = lineCount;
            shape.Lifetime = life;

            return shape;
        }
	}
}
